#pragma once


#include <memory.h>
#include <AVUIEventableObject.h>
#include <AVUIObjectPtr.h>
#include <AVUIAnimatable.h>
#include <AVUIIEnumerable.h>

namespace AVUI {

class ICollection
{
public:

    virtual bool get_IsEmpty()             = 0;
    virtual void Add(Object* pObject)      = 0;
	virtual void Insert(int index, Object* pObject) = 0;
    virtual void Clear()                   = 0;
    virtual bool Contains(Object* pObject) = 0;
    virtual bool Remove(Object* pObject)   = 0;
    virtual void RemoveAt(int idx) = 0;
    virtual int get_Count() = 0;
    virtual TRefCountedPtr<Object> GetItemAt(int idx) = 0;
	virtual void SetItemAt(int idx, Object* pValue) = 0;

    virtual int IndexOf(Object* pObject) = 0;
    virtual Object* get_Object() = 0;
};

class CollectionEnumerator : public Object, public IEnumerator
{
public:
    DECLARE_ELEMENT(CollectionEnumerator, Object);

    CollectionEnumerator(Object* pCollection) { m_pCollectionObject = pCollection; m_pCollection = interface_cast<ICollection>(m_pCollectionObject); Reset(); }
    virtual bool MoveNext() { m_iIndex++; if(m_iIndex >= m_pCollection->get_Count()) return false; return true; }
    virtual ObjectPtr get_Current() { return m_pCollection->GetItemAt(m_iIndex); }
    virtual void Reset() { m_iIndex = -1; }
    virtual bool IsInvalid() { return false; }
    virtual Object* get_Object() { return Object::get_Object(); }

private:
    TRefCountedPtr<Object> m_pCollectionObject;
    ICollection* m_pCollection;
    int m_iIndex;
};

class ObjectCollectionBase : public EventableObject, public ICollection, public IEnumerable
{
public:
    DECLARE_ELEMENT(ObjectCollectionBase, Object);
};

class AnimatableCollectionBase : public Animatable, public ICollection, public IEnumerable
{
public:
    DECLARE_ELEMENT(AnimatableCollectionBase, Animatable);
};


template <class T, class U>
class TCollectionBase : public U
{
	friend class ArrayList;
public:
    TCollectionBase(unsigned int cInitialCapacity = 8) : m_valuesTable(cInitialCapacity), m_version(0)
    { 
    }

    ~TCollectionBase() { Clear(); }

	void SetItemAt(int idx, T* pItem)
	{
		SetItem(idx, pItem);
	}

    void Add(T* pItem)
    {
		int count = get_Count();

		InsertItem(count, pItem);
    }

    void Insert(int index, T* pItem)
    {
		InsertItem(index, pItem);
    }

    void Clear() { ClearItems(); }

    bool Contains(T* pItem)  
    {
        return m_valuesTable.IndexOf(pItem) != -1;
    }

    bool Remove(T* pItem)
    {
		int index = IndexOf(pItem);
		if(index < 0)
		{
			return false;
		}

		RemoveAt(index);

		return true;
    }

    void RemoveAt(int idx)
    {
		RemoveItem(idx);
    }

	void RemoveRange(int idx, int count)
	{
		while(count > 0)
		{
			RemoveAt(idx);
			count--;
		}
	}

	int IndexOf(T* pItem)
	{
		int cCount = get_Count();

		for(int idx = 0; idx < cCount; idx++)
		{
			if(pItem == m_valuesTable[idx])
			{
				return idx;
			}
		}
		return -1;
	}


	void Move(int oldIdx, int newIdx)
	{
		// Minor deviation, move is not part of the collection interface
		TRefCountedPtr<T> pItem = (*this)[oldIdx];
		RemoveAt(oldIdx);
		Insert(newIdx, pItem);
	}

    T* operator[](int idx)
    {
        return m_valuesTable[idx];
    }

    int get_Count()
    {
        return m_valuesTable.get_Count();
    }

    bool get_IsEmpty() { return get_Count() == 0; }

    virtual ObjectPtr GetEnumerator() { return (Object*)object_allocate<CollectionEnumerator>(this); }

    void BlockWrite()
    {
        m_blockWrite = true;
    }

    void UnblockWrite()
    {
        m_blockWrite = false;
    }

    virtual Object* get_Object() { return Object::get_Object(); }

protected:

	virtual void ClearItems()
	{
		m_valuesTable.Clear();
	}

	virtual void InsertItem(int index, T* pItem)
	{
		m_valuesTable.Insert(index, pItem);
	}

	virtual void RemoveItem(int index)
	{
		m_valuesTable.RemoveAt(index);
	}

	virtual void SetItem(int index, T* pItem)
	{
		m_valuesTable[index] = pItem;
	}

private:

    ItemStructList< TRefCountedPtr<T> > m_valuesTable;

    bool m_blockWrite;
    unsigned int m_version;
};

template <class T, class U>
class TCollectionBaseWithInterface : public TCollectionBase<T, U>
{
public:
	void Add(Object* pObject)
	{
		if(!pObject->IsType(T::get_StaticType()))
		{
			LibraryCriticalError();
		}

		TCollectionBase<T, U>::Add(object_cast<T>(pObject));
	}

	void Insert(int idx, Object* pObject)
	{
		if(!pObject->IsType(T::get_StaticType()))
		{
			LibraryCriticalError();
		}

		TCollectionBase<T, U>::Insert(idx, object_cast<T>(pObject));
	}


	bool Contains(Object* pObject) 
	{
		if(!pObject->IsType(T::get_StaticType()))
		{
			return false;
		}

		return TCollectionBase<T, U>::Contains(object_cast<T>(pObject));
	}

	bool Remove(Object* pObject)
	{
		if(!pObject->IsType(T::get_StaticType()))
		{
			LibraryCriticalError();
		}

		return TCollectionBase<T, U>::Remove(object_cast<T>(pObject));
	}

	TRefCountedPtr<Object> GetItemAt(int idx)
	{
		return (*this)[idx];
	}

	void SetItemAt(int idx, Object* pValue)
	{
		if(pValue != NULL && object_cast<T>(pValue) == NULL)
		{
			LibraryCriticalError();
		}

		TCollectionBase<T, U>::SetItemAt(idx, object_cast<T>(pValue));
	}

	int IndexOf(Object* pValue)
	{
		if(pValue != NULL && object_cast<T>(pValue) == NULL)
		{
			LibraryCriticalError();
		}

		return TCollectionBase<T, U>::IndexOf(object_cast<T>(pValue));
	}
};



template <class T>
class TObjectCollection : public TCollectionBaseWithInterface<T, ObjectCollectionBase>
{

};

template <class T>
class TAnimatableCollection : public TCollectionBaseWithInterface<T, AnimatableCollectionBase>
{
public:

virtual void CloneCore(Freezable* pSource) AVUI_OVERRIDE
{
    TAnimatableCollection<T>* pCollectionSource = (TAnimatableCollection<T>*) pSource;
    Animatable::CloneCore(pSource);

    for(int idx = 0; idx < pCollectionSource->get_Count(); idx++)
    {
        Animatable* pAnimatable = object_cast<Animatable>(pCollectionSource->GetItemAt(idx));

        if(pAnimatable != NULL)
        {
            TCollectionBaseWithInterface<T, AnimatableCollectionBase>::Add(pAnimatable->Clone());
        }
    }

}

virtual void GetAsFrozenCore(Freezable* pSource) AVUI_OVERRIDE
{
    TAnimatableCollection<T>* pCollectionSource = (TAnimatableCollection<T>*) pSource;
    Animatable::GetAsFrozenCore(pSource);

    for(int idx = 0; idx < pCollectionSource->get_Count(); idx++)
    {
        Animatable* pAnimatable = object_cast<Animatable>(pCollectionSource->GetItemAt(idx));

        if(pAnimatable != NULL)
        {
            TCollectionBaseWithInterface<T, AnimatableCollectionBase>::Add(pAnimatable->GetAsFrozen());
        }
    }
}

protected:

	virtual void ClearItems()
	{
		TCollectionBase<T, AnimatableCollectionBase>::ClearItems();
		OnFreezablePropertyChanged(NULL, NULL); 
	}

	virtual void InsertItem(int index, T* pItem)
	{
		TCollectionBase<T, AnimatableCollectionBase>::InsertItem(index, pItem);
		OnFreezablePropertyChanged(NULL, pItem); 
	}

	virtual void RemoveItem(int index)
	{
		TRefCountedPtr<T> pOldValue = (*this)[index];
		TCollectionBase<T, AnimatableCollectionBase>::RemoveItem(index);

		OnFreezablePropertyChanged(pOldValue, NULL); 
	}

	virtual void SetItem(int index, T* pItem)
	{
		TRefCountedPtr<T> pOldValue = (*this)[index];
		TCollectionBase<T, AnimatableCollectionBase>::SetItem(index, pItem);

		OnFreezablePropertyChanged(pOldValue, pItem); 
	}
};

class ArrayList : public TCollectionBase<Object, ObjectCollectionBase>
{
public:
	DECLARE_ELEMENT(ArrayList, ObjectCollectionBase);

	ArrayList(int capacity = 8) : TCollectionBase<Object, ObjectCollectionBase>(capacity) { }
	ArrayList(ICollection* pCollection) 
	{ 
		for(int idx = 0; idx < pCollection->get_Count(); idx++)
		{
			Add(pCollection->GetItemAt(idx));
		}
	}

	TRefCountedPtr<Object> GetItemAt(int idx)
	{
		return (*this)[idx];
	}

	virtual int BinarySearch(Object* pValue)
	{
		return BinarySearch(0, get_Count(), pValue, NULL);
	}

	virtual int BinarySearch(Object* pValue, IComparer* pComparer)
	{
		return BinarySearch(0, get_Count(), pValue, pComparer);
	}

	virtual int BinarySearch(int index, int count, Object* pValue, IComparer* pComparer); 

};


}; // namespace AVUI
